// VideoCompressionService.java
package com.xiaoxu.intranetweb.mybatisplusSzappdb.timerTasks;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.*;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service

public class VideoCompressionService {

    private int videoRetentionDays;
    private static final Logger logger = LoggerFactory.getLogger(VideoCompressionService.class);
    private static final int MAX_RETRIES = 3;
    private static final long MIN_SIZE_MB = 100;
    private final Semaphore processingSemaphore = new Semaphore(50);

    // 使用自定义的IO线程池
    @Resource(name = "ioExecutor")
    private Executor ioExecutor;

//1小时
//@Scheduled(fixedDelay = 720000)
    public void processDailyVideos() throws IOException {
        if (!processingSemaphore.tryAcquire()) {
            logger.warn("视频压缩服务已达到最大并发限制");
            return;
        }

        try {
            Path targetDir = getTargetDirectory();
            if (targetDir == null) return;

            List<Path> videoFiles = listVideoFiles(targetDir);
            videoFiles.parallelStream().forEach(this::processVideo);
        } finally {
            processingSemaphore.release();
        }
    }

    private Path getTargetDirectory() throws IOException {
        String dateFolder = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
        Path dir = Paths.get("samplingVideos", dateFolder);
        return Files.exists(dir) ? dir : null;
    }

    private List<Path> listVideoFiles(Path dir) {
        try (Stream<Path> stream = Files.list(dir)) {
            return stream
                    .filter(this::isVideoFile)
                    .filter(this::isLargeFile)
                    .collect(Collectors.toList());
        } catch (IOException e) {
            logger.error("目录扫描失败: {}", dir, e);
            return Collections.emptyList();
        }
    }

    private boolean isVideoFile(Path path) {
        String name = path.getFileName().toString().toLowerCase();
        return name.endsWith(".mp4") || name.endsWith(".mov") || name.endsWith(".avi");
    }

    private boolean isLargeFile(Path path) {
        try {
            return Files.size(path) > MIN_SIZE_MB * 1024 * 1024;
        } catch (IOException e) {
            logger.warn("文件大小检查失败: {}", path, e);
            return false;
        }
    }

    private void processVideo(Path original) {
        Path tempFile = createTempFile(original);
        try {
            compressWithRetry(original, tempFile);
            safeReplace(original, tempFile);
        } catch (Exception e) {
            handleFailure(original, tempFile, e);
        } finally {
            quietlyDelete(tempFile);
        }
    }

    private Path createTempFile(Path original) {
        try {
            String tempName = "tmp_" + UUID.randomUUID() + "_" + original.getFileName();
            return Files.createTempFile("video_", tempName);
        } catch (IOException e) {
            throw new RuntimeException("临时文件创建失败", e);
        }
    }

    private void compressWithRetry(Path input, Path output) {
        int attempts = 0;
        while (attempts < MAX_RETRIES) {
            attempts++;
            try {
                runFfmpegProcess(input, output);
                if (validateCompressedFile(output)) return;
            } catch (Exception e) {
                if (attempts == MAX_RETRIES) {
                    throw new RuntimeException("压缩失败: " + input, e);
                }
                logger.warn("压缩重试中 ({}/{}): {}", attempts, MAX_RETRIES, input);
                sleepSafe();
            }
        }
    }

    private void runFfmpegProcess(Path input, Path output) throws IOException, InterruptedException {
        Process process = new ProcessBuilder(
                "ffmpeg", "-y",
                "-i", quotePath(input),
                "-vf", "scale='min(640,iw)':-2",
                "-c:v", "libx264",
                "-preset", "fast",
                "-crf", "24",
                quotePath(output)
        ).redirectErrorStream(true).start();

        logProcessOutput(process);
        checkExitCode(process);
    }

    private String quotePath(Path path) {
        return "\"" + path.toString().replace("\"", "\\\"") + "\"";
    }

    private void logProcessOutput(Process process) {
        CompletableFuture.runAsync(() -> {
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    logger.debug("[FFmpeg] {}", line);
                }
            } catch (IOException e) {
                Thread.currentThread().interrupt();
            }
        }, ioExecutor);
    }

    private void checkExitCode(Process process) throws InterruptedException, IOException {
        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new IOException("FFmpeg异常退出，代码: " + exitCode);
        }
    }

    private boolean validateCompressedFile(Path file) {
        try {
            return Files.size(file) > 1024 * 1024 &&
                    checkVideoIntegrity(file);
        } catch (IOException e) {
            return false;
        }
    }

    private boolean checkVideoIntegrity(Path file) {
        try {
            Process process = new ProcessBuilder("ffprobe", "-v", "error", quotePath(file))
                    .redirectErrorStream(true)
                    .start();
            return process.waitFor() == 0;
        } catch (IOException | InterruptedException e) {
            return false;
        }
    }

    private void safeReplace(Path original, Path tempFile) throws IOException {
        Path lockFile = original.resolveSibling(original.getFileName() + ".lock");
        try (FileChannel channel = FileChannel.open(lockFile,
                StandardOpenOption.CREATE,
                StandardOpenOption.WRITE);
             FileLock lock = channel.tryLock()) {

            if (lock == null) throw new IOException("文件被锁定: " + original);

            // 三阶段提交
            Path staging = original.resolveSibling(original.getFileName() + ".staging");
            Files.move(tempFile, staging, StandardCopyOption.REPLACE_EXISTING);

            Path backup = original.resolveSibling(original.getFileName() + ".bak");
            Files.move(original, backup, StandardCopyOption.REPLACE_EXISTING);

            Files.move(staging, original, StandardCopyOption.ATOMIC_MOVE);
            Files.deleteIfExists(backup);

        } finally {
            Files.deleteIfExists(lockFile);
        }
    }

    private void handleFailure(Path original, Path tempFile, Exception e) {
        logger.error("视频处理失败: {} -> {}", original, e.getMessage());
        logger.debug("失败详情", e);
        quietlyDelete(tempFile);
    }

    private void sleepSafe() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private void quietlyDelete(Path path) {
        try {
            Files.deleteIfExists(path);
        } catch (IOException ignored) {}
    }



    /**
     * 每天凌晨 0:00 执行，清理一个月前的视频目录
     */
    @Value("${app.video-retention-days}")
    @Scheduled(cron = "0 0 0 * * ?") // 每天 0 点执行
//    @Scheduled(fixedDelay = 10000)
    public void cleanOldVideos() {
        try {
            // 设置保留最近 30 天数据
            LocalDate cutoffDate = LocalDate.now().minusDays(30);
            DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
            String targetFolderName = "samplingVideos";

            Path baseDir = Paths.get(targetFolderName);

            if (!Files.exists(baseDir)) {
                logger.info("基础目录不存在：{}", baseDir.toAbsolutePath());
                return;
            }

            try (Stream<Path> stream = Files.list(baseDir)) {
                stream.forEach(path -> {
                    if (Files.isDirectory(path)) {
                        String dirName = path.getFileName().toString();
                        try {
                            LocalDate dirDate = LocalDate.parse(dirName, formatter);
                            if (dirDate.isBefore(cutoffDate)) {
                                deleteDirectoryRecursively(path);
                                logger.info("已删除旧目录：{}", path);
                            }
                        } catch (Exception e) {
                            logger.warn("跳过非日期格式目录：{}", path);
                        }
                    }
                });
            }
        } catch (Exception e) {
            logger.error("清理旧视频失败", e);
        }
    }
    private void deleteDirectoryRecursively(Path directory) throws IOException {
        if (Files.exists(directory)) {
            try (Stream<Path> stream = Files.walk(directory)) {
                stream.sorted(Comparator.reverseOrder())
                        .forEach(path -> {
                            try {
                                Files.delete(path);
                            } catch (IOException e) {
                                logger.warn("无法删除文件：{}", path, e);
                            }
                        });
            }
        }
    }

}